#ifndef _CUDA_SOLVER_SOLVER_LCPCG_CUDA_H_
#define _CUDA_SOLVER_SOLVER_LCPCG_CUDA_H_

#include <windows.h>
#include <cuda.h>
#include "dynamics\solver_base.h"


#define HARDWARE_INTEGRATION 1



#define CUDA_SAFE_CALL(func)\
		{\
			CUresult res = func;\
			assert(res == CUDA_SUCCESS);\
		}

#define CUDA_ERROR_ASSERT(res) \
		assert(res != CUDA_ERROR_DEINITIALIZED); \
		assert(res != CUDA_ERROR_NOT_INITIALIZED); \
		assert(res != CUDA_ERROR_INVALID_CONTEXT); \
		assert(res != CUDA_ERROR_INVALID_VALUE); \
		assert(res != CUDA_ERROR_LAUNCH_FAILED); \
		assert(res != CUDA_ERROR_OUT_OF_MEMORY); \
		assert(res != CUDA_ERROR_NO_DEVICE); \
		assert(res != CUDA_ERROR_INVALID_DEVICE); \
		assert(res != CUDA_ERROR_INVALID_IMAGE); \
		assert(res != CUDA_ERROR_CONTEXT_ALREADY_CURRENT); \
		assert(res != CUDA_ERROR_MAP_FAILED); \
		assert(res != CUDA_ERROR_UNMAP_FAILED); \
		assert(res != CUDA_ERROR_NO_BINARY_FOR_GPU); \
		assert(res != CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES); \
		assert(res != CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING); \
		assert(res != CUDA_ERROR_LAUNCH_TIMEOUT); \
		assert(res != CUDA_ERROR_UNKNOWN); \
		assert(res == CUDA_SUCCESS);



#define GET_UPPER_ALIGNMENT_BOUND(offset, data) \
			((offset) + __alignof(data) - 1) & ~(__alignof(data) - 1)

#define ASSIGN_TEXTURE_REF_KERNEL_FULL(arr_name, cu_kernel_function, byte_size) \
	CUDA_SAFE_CALL( cuModuleGetTexRef(&texref_##arr_name, m_cuModule, "texref_m_" #arr_name) ); \
	{ \
		unsigned int ByteOffset; \
		CUDA_SAFE_CALL( cuTexRefSetAddress(&ByteOffset, texref_##arr_name, m_gpu_##arr_name, byte_size); ); \
	} \
	CUDA_SAFE_CALL( cuParamSetTexRef(cu_kernel_function, CU_PARAM_TR_DEFAULT, texref_##arr_name) );

#define ASSIGN_ALIGNED_MEM_TEXTURE_REF(arr_name, byte_size, texref_format) \
	CUDA_SAFE_CALL( cuMemAlloc(&m_gpu_##arr_name, byte_size) ); \
	CUDA_SAFE_CALL( cuModuleGetTexRef(&texref_##arr_name, m_cuModule, "texref_m_" #arr_name) ); \
	{ \
		unsigned int ByteOffset; \
		CUDA_SAFE_CALL( cuTexRefSetAddress(&ByteOffset, texref_##arr_name, m_gpu_##arr_name, byte_size); ); \
	} \
	CUDA_SAFE_CALL( cuTexRefSetFormat(texref_##arr_name, texref_format, 1) );

#define ASSIGN_ALIGNED_MEM_TEXTURE_REF_NOALLOC(arr_name, byte_size, texref_format) \
	CUDA_SAFE_CALL( cuModuleGetTexRef(&texref_##arr_name, m_cuModule, "texref_m_" #arr_name) ); \
	{ \
		unsigned int ByteOffset; \
		CUDA_SAFE_CALL( cuTexRefSetAddress(&ByteOffset, texref_##arr_name, m_gpu_##arr_name, byte_size); ); \
	} \
	CUDA_SAFE_CALL( cuTexRefSetFormat(texref_##arr_name, texref_format, 1) );

#define ASSIGN_TEXTURE_REF_KERNEL(arr_name, cu_kernel_function) \
	CUDA_SAFE_CALL( cuParamSetTexRef(cu_kernel_function, CU_PARAM_TR_DEFAULT, texref_##arr_name) );

#define ASSIGN_MEM_CONSTANT_PTR(arr_name, byte_size) \
    CUDA_SAFE_CALL( cuMemAlloc(&m_gpu_##arr_name, byte_size) ); \
	{ \
		unsigned int SymbolMemSize; \
		CUdeviceptr Symbol; \
		cuModuleGetGlobal(&Symbol, &SymbolMemSize, m_cuModule, "ptr_m_" #arr_name); \
		cuMemsetD32(Symbol, m_gpu_##arr_name, 1); \
	}
#define ASSIGN_MEM_CONSTANT_PTR_NOALLOC(arr_name) \
	{ \
		unsigned int SymbolMemSize; \
		CUdeviceptr Symbol; \
		cuModuleGetGlobal(&Symbol, &SymbolMemSize, m_cuModule, "ptr_m_" #arr_name); \
		cuMemsetD32(Symbol, m_gpu_##arr_name, 1); \
	}

#define COPY_MEM_H_TO_D(arr_name, byte_size, byte_offset_gpu, byte_offset_cpu) \
	CUDA_SAFE_CALL( cuMemcpyHtoD(m_gpu_##arr_name + (byte_offset_gpu), ptr_m_##arr_name + (byte_offset_cpu), byte_size) ); \
/*
#define COPY_MEM_H_TO_D(arr_name, byte_size, byte_offset) \
	CUDA_SAFE_CALL( cuMemcpyHtoD(m_gpu_##arr_name, ptr_m_##arr_name, byte_size) );
*/


// Same as ASSIGN_<..> but with copying host memory to gpu-device memory
#define COPY_MEM_CONSTANT_PTR(arr_name, byte_size) \
	CUdeviceptr gpu_##arr_name; \
    CUDA_SAFE_CALL( cuMemAlloc(&gpu_##arr_name, byte_size) ); \
	CUDA_SAFE_CALL( cuMemcpyHtoD(gpu_##arr_name, ptr_##arr_name, byte_size) ); \
	{ \
		unsigned int SymbolMemSize; \
		CUdeviceptr Symbol; \
		cuModuleGetGlobal(&Symbol, &SymbolMemSize, m_cuModule, "ptr_" #arr_name); \
		cuMemsetD32(Symbol, gpu_##arr_name, 1); \
	}

class SolverLCPCG_CUDA : public SolverBase
{
	friend class FEMJoint;		// list all joints here; in future make some kind of interface for SOlver tompass to joints
	friend class BallSocket;	// list all joints here; in future make some kind of interface for SOlver tompass to joints

public:

	SolverLCPCG_CUDA(unsigned int MaxIterations, float Precision) : SolverBase(MaxIterations, Precision)
	{
/*
		CUresult res = InitCUDA();
		CUDA_ERROR_ASSERT(res);
*/
	}

	~SolverLCPCG_CUDA()
	{
		DeinitCUDA();
	}

	void Solve(float dt);

	void SendInitParams_GPU();

	CUresult InitCUDA(unsigned int device);

	CUdevice m_cuDevice;
	CUmodule m_cuModule;
	CUcontext m_cuContext;
	CUdevprop m_GPU_prop;
	CUfunction	m_KernelFunction,
				m_ASparseCalcFunction,
				m_IntegrateFunction,
				m_JacobiCalcFunction;

	int m_MultiProcessorsNum;

	char *m_MainPTX_Image;

	dynarray<float, MAX_NUM_JACOBI> m_ad_vec;								// vector A * d
	dynarray<float, MAX_NUM_JACOBI> m_ap_vec;								// vector A * p

	dynarray<float, MAX_NUM_JACOBI> m_p;									// stepping vector
	dynarray<float, MAX_NUM_JACOBI> m_d;									// stepping vector, also intermediate in Power Iteration
	dynarray<float, MAX_NUM_JACOBI> m_g;									// gradient vector

	// Indices of joints, users of particular node
	dynarray<unsigned int, MAX_NUM_JACOBI * NUM_JOINT_TRIPLES>	m_NodesJoints_00, m_NodesJoints_01, m_NodesJoints_02,
																m_NodesJoints_03, m_NodesJoints_04, m_NodesJoints_05;
	// Number of joints using node
	dynarray<unsigned int, MAX_NUM_NODES>	m_NodesJointsNum_00, m_NodesJointsNum_01, m_NodesJointsNum_02,
											m_NodesJointsNum_03, m_NodesJointsNum_04, m_NodesJointsNum_05;

	// Offset for node in m_NodesJoints
	dynarray<unsigned int, MAX_NUM_NODES>	m_NodesJointsOffset_00, m_NodesJointsOffset_01, m_NodesJointsOffset_02,
											m_NodesJointsOffset_03, m_NodesJointsOffset_04, m_NodesJointsOffset_05;
private:

	enum KernelTypes
	{
		SOLVER = 0,
		ASPARSE,
		INTEGRATE,
		JACOBI
	};

	void DeinitCUDA();

	unsigned int m_SharedMemorySizes[4];
	unsigned int m_SharedMemPerBlock;

	unsigned int m_CUDADeviceMultiplier;

	CUtexref	texref_Asp_00, texref_Asp_01, texref_Asp_02,
				texref_Asp_03, texref_Asp_04, texref_Asp_05,
				texref_Asp_06, texref_Asp_07, texref_Asp_08,
				texref_Asp_09, texref_Asp_10, texref_Asp_11,
				texref_Asp_12, texref_Asp_13, texref_Asp_14,
				texref_Asp_15, texref_Asp_16, texref_Asp_17;

	CUtexref	texref_NodesJoints_00, texref_NodesJoints_01, texref_NodesJoints_02,
				texref_NodesJoints_03, texref_NodesJoints_04, texref_NodesJoints_05;

	CUdeviceptr	m_gpu_Asp_00, m_gpu_Asp_01, m_gpu_Asp_02,
				m_gpu_Asp_03, m_gpu_Asp_04, m_gpu_Asp_05,
				m_gpu_Asp_06, m_gpu_Asp_07, m_gpu_Asp_08,
				m_gpu_Asp_09, m_gpu_Asp_10, m_gpu_Asp_11,
				m_gpu_Asp_12, m_gpu_Asp_13, m_gpu_Asp_14,
				m_gpu_Asp_15, m_gpu_Asp_16, m_gpu_Asp_17;

	CUdeviceptr m_gpu_J_00, m_gpu_J_01, m_gpu_J_02,
				m_gpu_J_03, m_gpu_J_04, m_gpu_J_05,
				m_gpu_J_06, m_gpu_J_07, m_gpu_J_08,
				m_gpu_J_09, m_gpu_J_10, m_gpu_J_11,
				m_gpu_J_12, m_gpu_J_13, m_gpu_J_14,
				m_gpu_J_15, m_gpu_J_16, m_gpu_J_17;

	CUdeviceptr m_gpu_JtdNodes_00, m_gpu_JtdNodes_01, m_gpu_JtdNodes_02,
				m_gpu_JtdNodes_03, m_gpu_JtdNodes_04, m_gpu_JtdNodes_05;

	CUdeviceptr m_gpu_CFM;


	CUdeviceptr	m_gpu_NodesJoints_00, m_gpu_NodesJoints_01, m_gpu_NodesJoints_02,
				m_gpu_NodesJoints_03, m_gpu_NodesJoints_04, m_gpu_NodesJoints_05;

	CUdeviceptr m_gpu_NodesJointsNum_00, m_gpu_NodesJointsNum_01, m_gpu_NodesJointsNum_02,
				m_gpu_NodesJointsNum_03, m_gpu_NodesJointsNum_04, m_gpu_NodesJointsNum_05;

	CUdeviceptr	m_gpu_NodesJointsOffset_00, m_gpu_NodesJointsOffset_01, m_gpu_NodesJointsOffset_02,
				m_gpu_NodesJointsOffset_03, m_gpu_NodesJointsOffset_04, m_gpu_NodesJointsOffset_05;


	CUdeviceptr m_gpu_lambda;
	CUdeviceptr m_gpu_invDiag;
	CUdeviceptr m_gpu_Lo, m_gpu_Hi;
	CUdeviceptr m_gpu_RHS;


	CUdeviceptr m_gpu_d;
	CUdeviceptr m_gpu_p;
	CUdeviceptr m_gpu_g;
	CUdeviceptr m_gpu_ap_vec, m_gpu_ad_vec;

	CUdeviceptr m_gpu_a_x, m_gpu_a_y, m_gpu_a_z;

	CUdeviceptr m_gpu_internal_results, m_gpu_internal_results_1, m_gpu_internal_results_2, m_gpu_internal_results_3;

//////////////////////////////////////////////////////////////////////////

	CUdeviceptr m_gpu_NodeInvMass0_00, m_gpu_NodeInvMass0_01, m_gpu_NodeInvMass0_02,
				m_gpu_NodeInvMass0_10, m_gpu_NodeInvMass0_11, m_gpu_NodeInvMass0_12,
				m_gpu_NodeInvMass0_20, m_gpu_NodeInvMass0_21, m_gpu_NodeInvMass0_22;

	CUdeviceptr m_gpu_NodeInvMass_00, m_gpu_NodeInvMass_01, m_gpu_NodeInvMass_02,
				m_gpu_NodeInvMass_10, m_gpu_NodeInvMass_11, m_gpu_NodeInvMass_12,
				m_gpu_NodeInvMass_20, m_gpu_NodeInvMass_21, m_gpu_NodeInvMass_22;

	CUtexref	texref_NodeInvMass_00, texref_NodeInvMass_01, texref_NodeInvMass_02,
				texref_NodeInvMass_10, texref_NodeInvMass_11, texref_NodeInvMass_12,
				texref_NodeInvMass_20, texref_NodeInvMass_21, texref_NodeInvMass_22;

	CUdeviceptr m_gpu_NodeF_x, m_gpu_NodeF_y, m_gpu_NodeF_z;

	CUdeviceptr m_gpu_Ftot_x, m_gpu_Ftot_y, m_gpu_Ftot_z;
	CUdeviceptr m_gpu_NodeVel_x, m_gpu_NodeVel_y, m_gpu_NodeVel_z;

	CUdeviceptr m_gpu_NodePosRot_x, m_gpu_NodePosRot_y, m_gpu_NodePosRot_z, m_gpu_NodePosRot_w;

	CUdeviceptr m_gpu_IsRotational;







	CUdeviceptr	m_gpu_Joint_MemDmp_00, m_gpu_Joint_MemDmp_01, m_gpu_Joint_MemDmp_02, m_gpu_Joint_MemDmp_03,
				m_gpu_Joint_MemDmp_04, m_gpu_Joint_MemDmp_05, m_gpu_Joint_MemDmp_06, m_gpu_Joint_MemDmp_07,
				m_gpu_Joint_MemDmp_08, m_gpu_Joint_MemDmp_09, m_gpu_Joint_MemDmp_10, m_gpu_Joint_MemDmp_11;







	//////////////////////////////////////////////////////////////////////////
	// GPU Jacobi calculation
	//////////////////////////////////////////////////////////////////////////
public:

	void SendFEMInitParams_GPU(std::vector<FEMJoint, GlobalAllocator<FEMJoint>> &FEM_Joints);

	void SendFEMParams_GPU(std::vector<FEMJoint, GlobalAllocator<FEMJoint>> &FEM_Joints);
	void CalcFEMParams_GPU(float dt);

	unsigned int m_Num_FEM_Joints;

private:

/*
texture<float, 1, cudaReadModeElementType>	texref_m_FEM_Regularization;
texture<float, 1, cudaReadModeElementType>	texref_m_FEM_CFM, texref_m_FEM_Jp0, texref_m_FEM_RHS;
texture<float, 1, cudaReadModeElementType>	texref_m_FEM_B_loc;

texture<float, 1, cudaReadModeElementType>	texref_m_NodePosRot_x, texref_m_NodePosRot_y,
											texref_m_NodePosRot_z, texref_m_NodePosRot_w;

texture<unsigned int, 1, cudaReadModeElementType>	texref_m_FEM_N1_Idx, texref_m_FEM_N2_Idx,
													texref_m_FEM_N3_Idx, texref_m_FEM_N4_Idx;

__constant__ float	*ptr_m_FEM_N_00, *ptr_m_FEM_N_01, *ptr_m_FEM_N_02,
					*ptr_m_FEM_N_10, *ptr_m_FEM_N_11, *ptr_m_FEM_N_12,
					*ptr_m_FEM_N_20, *ptr_m_FEM_N_21, *ptr_m_FEM_N_22;

__constant__ float *ptr_m_FEM_E_plastic, *ptr_m_FEM_MaxPlasticStrain, *ptr_m_FEM_Yield, *ptr_m_FEM_Creep;
__constant__ float *ptr_m_FEM_Damping;*/


	// Texture references
	CUtexref	texref_FEM_Regularization;
	CUtexref	texref_CFM, texref_FEM_E_plastic, texref_FEM_Jp0, texref_RHS, texref_invDiag;
	CUtexref	texref_FEM_B_loc;
	CUtexref	texref_FEM_N_00, texref_FEM_N_01, texref_FEM_N_02,
				texref_FEM_N_10, texref_FEM_N_11, texref_FEM_N_12,
				texref_FEM_N_20, texref_FEM_N_21, texref_FEM_N_22;
	CUtexref	texref_FEM_N1_Idx, texref_FEM_N2_Idx, texref_FEM_N3_Idx, texref_FEM_N4_Idx;

	CUtexref	texref_NodePosRot_x, texref_NodePosRot_y, texref_NodePosRot_z, texref_NodePosRot_w;
	CUtexref	texref_Ftot_x, texref_Ftot_y, texref_Ftot_z;

	// Device pointers
	CUdeviceptr m_gpu_FEM_Damping, m_gpu_FEM_Regularization;
	CUdeviceptr	m_gpu_FEM_E_plastic, m_gpu_FEM_Jp0, m_gpu_FEM_CFM;
	CUdeviceptr	m_gpu_FEM_B_loc;
	CUdeviceptr m_gpu_FEM_N_00, m_gpu_FEM_N_01, m_gpu_FEM_N_02,
				m_gpu_FEM_N_10, m_gpu_FEM_N_11, m_gpu_FEM_N_12,
				m_gpu_FEM_N_20, m_gpu_FEM_N_21, m_gpu_FEM_N_22;
	CUdeviceptr	m_gpu_FEM_N1_Idx, m_gpu_FEM_N2_Idx, m_gpu_FEM_N3_Idx, m_gpu_FEM_N4_Idx;
	CUdeviceptr	m_gpu_FEM_MaxPlasticStrain, m_gpu_FEM_Yield, m_gpu_FEM_Creep;


	CUevent m_EventSync_Jacobi;


	// Host arrays
	dynarray<float, MAX_NUM_JACOBI> m_FEM_Damping, m_FEM_Regularization,
									m_FEM_MaxPlasticStrain, m_FEM_Yield, m_FEM_Creep;
	dynarray<float, 6 * MAX_NUM_JACOBI> m_FEM_E_plastic, m_FEM_Jp0, m_FEM_CFM;
	
	dynarray<float, 6 * 12 * MAX_NUM_JACOBI> m_FEM_B_loc;

	dynarray<float, 6 * 12 * MAX_NUM_JACOBI> m_FEM_lambda_IG;

	dynarray<float, MAX_NUM_JACOBI> m_FEM_N_00, m_FEM_N_01, m_FEM_N_02,
									m_FEM_N_10, m_FEM_N_11, m_FEM_N_12,
									m_FEM_N_20, m_FEM_N_21, m_FEM_N_22;

	dynarray<unsigned int, MAX_NUM_JACOBI> m_FEM_N1_Idx, m_FEM_N2_Idx, m_FEM_N3_Idx, m_FEM_N4_Idx;
};

#endif